<?php
// +----------------------------------------------------------------------
// | APS智能排期 类型服务
// +----------------------------------------------------------------------
// | 主要服务于APS智能排期数据处理
// +----------------------------------------------------------------------
// | 错误编码头 246xxx
// +----------------------------------------------------------------------

namespace common\service;


use common\exception\LogicException;
use common\model\EntityModel;
use common\model\PlanModel;
use common\model\StepCategoryModel;
use common\model\TaskModel;
use common\model\TaskRelationModel;
use support\bootstrap\Redis;
use support\ErrorCode;
use teamones\Request;
use think\Exception;
use think\module\Module;
use Webman\Stomp\Client;

class ApsService
{
    // 当前排期的开始时间
    protected $timeRangeStartTime = 0;

    // 当前排期的结束时间
    protected $timeRangeEndTime = 0;

    // 当前排期的时间长度
    protected $timeRangeLength = 0;

    // 可以分配任务数据
    protected $canBeScheduledTaskData = [];

    protected $dayHourLength = 9;

    /**
     * 当前可排期范围, 转化成多少工时
     *
     * 1、每天工作多少小时，法定8小时（可配置） TODO
     * 2、排除节假日和休息时间 TODO
     *
     * @param $startTime
     * @param $endTime
     * @return int
     */
    protected function countTimeRangeLength($startTime, $endTime)
    {
        $timeRangeLength = (int)((ceil(($endTime - $startTime) / 86400)) * $this->dayHourLength);
        $this->timeRangeLength = $timeRangeLength;
        return $timeRangeLength;
    }

    /**
     * 获取当前分类的排期时间范围
     * @param $projectId
     * @param $stepCategoryId
     * @return int
     */
    protected function getStepCategoryTimeRange($projectId, $stepCategoryId)
    {
        // 获取当前分类的排期时间范围
        $planModel = model(PlanModel::class);
        $timeRange = $planModel->field('start_time,end_time')->where(['project_id' => $projectId, 'step_category_id' => $stepCategoryId])->find();

        if (!(!empty($timeRange) && $timeRange['start_time'] > 0 && $timeRange['end_time'] > 0 && $timeRange['end_time'] > $timeRange['start_time'])) {
            // 时间范围不存在或者开始时间和截止时间范围不合理
            throw_strack_exception("The time range does not exist or the start time and deadline range are unreasonable.", ErrorCode::TIME_RANGE_UNREASONABLE);
        }

        $timeRangeLength = $this->countTimeRangeLength($timeRange['start_time'], $timeRange['end_time']);

        $this->timeRangeStartTime = $timeRange['start_time'];
        $this->timeRangeEndTime = $timeRange['end_time'];


        return $timeRangeLength;
    }

    /**
     * 获取当前工序分类绑定的实体参数
     * @param $stepCategoryId
     * @return mixed
     */
    protected function getStepCategoryBindEntityParam($stepCategoryId)
    {
        $stepCategoryModel = model(StepCategoryModel::class);
        $entityId = $stepCategoryModel->where(['id' => $stepCategoryId])->getField('entity_id');

        if (empty($entityId)) {
            throw_strack_exception("Step category unbound entity.", ErrorCode::STEP_CATEGORY_UNBOUND_ENTITY);
        }

        return Module::$moduleDictData['module_index_by_id'][$entityId];
    }

    /**
     * 获取任务级别
     * @param $levelCategory
     * @return mixed|string
     */
    protected function getTaskLevel($levelCategory)
    {
        if (empty($levelCategory) && strpos($levelCategory, '-') === false) {
            return '';
        }

        // 需要按级别分类统计 s-15
        $levelArray = explode('-', $levelCategory);

        return $levelArray[0];
    }

    /**
     * 获取实体下面工序分类任务最大消耗工时
     * @param $entityIds
     * @param $entityDataDict
     * @param $entityModuleId
     * @return array
     * @throws \think\Exception
     */
    protected function getStepCategoryTaskMaxManHour($entityIds, $entityDataDict, $entityModuleId)
    {
        $taskModel = model(TaskModel::class);

        $taskQueryFields = [
            'task.id',
            'task.man_hour',
            'task.step_category_id',
            'task.entity_id',
            'task.is_assignee',
            'task.is_manual_adjustment',
            'task.level_category',
            'task.capacity_mark',
            'task.plan_start_time',
            'task.plan_end_time',
            'entity.module_id',
            'entity.step_workflow_id',
            "entity.cost_hours",
            "entity.estimate_hours",
        ];

        $stepCategoryTaskData = $taskModel->field(implode(',', $taskQueryFields))
            ->join("entity ON entity.id = task.entity_id")
            ->where([
                'task.entity_id' => ['in', join(',', $entityIds)],
                'task.entity_module_id' => $entityModuleId
            ])
            ->select();
        $userCertService = UserCertService::getInstance();
        $stepSkuService = StepSkuService::getInstance();
        // 按照实体分组
        $stepCategoryMaxTaskMap = [];
        foreach ($stepCategoryTaskData as $stepCategoryTaskItem) {

            list(, $stepSkuIds) = $stepSkuService->autoCalculationManHourByEntity($stepCategoryTaskItem['entity_id'], $stepCategoryTaskItem['module_id'], 'step_category', $stepCategoryTaskItem['step_category_id'], $stepCategoryTaskItem['step_workflow_id']);
            $matchCertStr = '';
            $matchCert = $userCertService->matchUserCertBySku($stepSkuIds);
            $certId = 0;
            if (!empty($matchCert)) {
                $certId = $matchCert['id'];
                $matchCertStr = "{$matchCert['level_category']}-{$matchCert['level']}";
            }
            if (!empty($matchCertStr) && $stepCategoryTaskItem['level_category'] != $matchCertStr) {
                // 修正证书标识
                $taskModel->save([
                    'id' => $stepCategoryTaskItem['id'],
                    'level_category' => $matchCertStr,
                    'user_cert_id' => $certId
                ]);
            }
            $stepCategoryTaskItem['level_category'] = $matchCertStr;
            //工时修正
            $stepCategoryTaskItem['man_hour'] = $stepCategoryTaskItem['man_hour'] * ($stepCategoryTaskItem['cost_hours'] / $stepCategoryTaskItem['estimate_hours']);


            $level = '';

            if (!empty($stepCategoryTaskItem['level_category'])) {
                $level = $this->getTaskLevel($stepCategoryTaskItem['level_category']);
            }

            if (array_key_exists($stepCategoryTaskItem['entity_id'], $stepCategoryMaxTaskMap)) {
                if ($stepCategoryTaskItem['man_hour'] > $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['max_hour']) {
                    $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['max_hour'] = $stepCategoryTaskItem['man_hour'];
                }

                if (!empty($level)) {
                    $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['level_category'] = $level;
                }

                if ($stepCategoryTaskItem['is_manual_adjustment'] === 'yes') {
                    $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['is_manual_adjustment'] = $stepCategoryTaskItem['is_manual_adjustment'];
                }

                if (!empty($stepCategoryTaskItem['plan_start_time']) && $stepCategoryTaskItem['plan_start_time'] < $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['max_task_start_time']) {
                    $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['max_task_start_time'] = $stepCategoryTaskItem['plan_start_time'];
                }

                if ($stepCategoryTaskItem['plan_end_time'] > $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['max_task_end_time']) {
                    $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['max_task_end_time'] = $stepCategoryTaskItem['plan_end_time'];
                }

                if ($stepCategoryTaskItem['is_assignee'] === 'yes') {
                    $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']]['is_assignee'] = $stepCategoryTaskItem['is_assignee'];
                }
            } else {
                $stepCategoryMaxTaskMap[$stepCategoryTaskItem['entity_id']] = [
                    'id' => $entityDataDict[$stepCategoryTaskItem['entity_id']]['id'],
                    'man_hour' => $entityDataDict[$stepCategoryTaskItem['entity_id']]['man_hour'],
                    'max_hour' => $stepCategoryTaskItem['man_hour'],
                    'max_task_start_time' => !empty($stepCategoryTaskItem['plan_start_time']) ? $stepCategoryTaskItem['plan_start_time'] : 9888888888,
                    'max_task_end_time' => $stepCategoryTaskItem['plan_end_time'],
                    'level' => $level,
                    'capacity_mark' => $stepCategoryTaskItem['capacity_mark'],
                    'is_assignee' => $stepCategoryTaskItem['is_assignee'],
                    'is_manual_adjustment' => $stepCategoryTaskItem['is_manual_adjustment'],
                    'start_time' => $entityDataDict[$stepCategoryTaskItem['entity_id']]['start_time'],
                    'end_time' => $entityDataDict[$stepCategoryTaskItem['entity_id']]['end_time']
                ];
            }
        }

        /**
         * 如果已经分配
         * 1、忽略已经分配没有产能标识的任务
         * 2、已经分配也有产能标识修正 start_time 和 end_time
         */

        $stepCategoryTaskNeedPlanData = [];
        foreach ($stepCategoryMaxTaskMap as $entityId => $stepCategoryTask) {
            if (!($stepCategoryTask['is_assignee'] === 'yes' && empty($stepCategoryTask['capacity_mark']))) {
                // 忽略已经分配没有产能标识的任务

                if ($stepCategoryTask['is_assignee'] === 'yes' && !empty($stepCategoryTask['capacity_mark'])) {
                    // 已经分配也有产能标识修正 start_time 和 end_time
                    if (empty($stepCategoryTask['start_time'])) {
                        $stepCategoryTask['start_time'] = $stepCategoryTask['max_task_start_time'];
                    }

                    if (empty($stepCategoryTask['end_time'])) {
                        $stepCategoryTask['end_time'] = $stepCategoryTask['max_task_end_time'];
                    }
                } else if ($stepCategoryTask['is_manual_adjustment'] === 'yes') {
                    // 已经分配也有产能标识修正 start_time 和 end_time
                    if (empty($stepCategoryTask['start_time'])) {
                        $stepCategoryTask['start_time'] = $stepCategoryTask['max_task_start_time'];
                    }

                    if (empty($stepCategoryTask['end_time'])) {
                        $stepCategoryTask['end_time'] = $stepCategoryTask['max_task_end_time'];
                    }
                } else {
                    $stepCategoryTask['capacity_mark'] = '';
                }

                if (in_array($stepCategoryTask['level'], ['s', 'a', 'b', 'c'])) {
                    $stepCategoryTaskNeedPlanData[$entityId] = $stepCategoryTask;
                }
            }
        }

        return $stepCategoryTaskNeedPlanData;
    }

    /**
     * 获取需要排期工序大类任务下面子任务最大消耗工时
     * @param $taskStepCategoryData
     * @return array
     */
    protected function getStepCategorySubTaskMaxManHour($taskStepCategoryData)
    {
        /**
         * 查找当前需要自动排期任务下级关联任务并生成索引字典
         */
        $needAutoSchedulingTaskIds = array_column($taskStepCategoryData, 'id');
        $taskRelationModel = model(TaskRelationModel::class);
        $targetTaskIdData = $taskRelationModel->field('id,source_task_id,target_task_id')
            ->where(['source_task_id' => ['IN', join(',', $needAutoSchedulingTaskIds)]])
            ->select();

        if (empty($targetTaskIdData)) {
            // 未生成工序任务
            throw_strack_exception("No step task generated.", ErrorCode::STEP_NO_TASK_GENERATED);
        }

        // 查找子集任务
        $taskModel = model(TaskModel::class);
        $stepTaskData = $taskModel->field('id,man_hour,step_category_id')
            ->where(['id' => ['in', join(',', array_column($targetTaskIdData, 'target_task_id'))]])
            ->select();

        $stepCategoryMaxTaskMap = [];
        foreach ($stepTaskData as $stepTaskItem) {
            if (array_key_exists($stepTaskItem['step_category_id'], $stepCategoryMaxTaskMap)) {
                if ($stepTaskItem['man_hour'] > $stepCategoryMaxTaskMap[$stepTaskItem['step_category_id']]) {
                    $stepCategoryMaxTaskMap[$stepTaskItem['step_category_id']] = $stepTaskItem['man_hour'];
                }
            } else {
                $stepCategoryMaxTaskMap[$stepTaskItem['step_category_id']] = $stepTaskItem['man_hour'];
            }
        }

        return $stepCategoryMaxTaskMap;
    }

    /**
     * 统计已经存在产能数据
     * @param $exitCapacity
     * @param $taskData
     */
    protected function checkExitCapacity(&$exitCapacity, $taskData)
    {
        foreach ($taskData as $isAssigneeTaskItem) {

            // 当前产能前面是否能追加产能
            $preResidueManHour = 0;

            // 当前产能后面是否能追加产能
            $nextResidueManHour = 0;
            if ($isAssigneeTaskItem['end_time'] < $this->timeRangeEndTime) {
                // 不足一个小时舍弃
                $nextResidueManHour = floor($isAssigneeTaskItem['start_time'] / 3600) + $isAssigneeTaskItem['max_hour'];
            }

            if (array_key_exists($isAssigneeTaskItem['capacity_mark'], $exitCapacity)) {
                if ($isAssigneeTaskItem['start_time'] < $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['start_time']) {
                    $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['start_time'] = $isAssigneeTaskItem['start_time'];
                }

                if ($isAssigneeTaskItem['end_time'] > $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['end_time']) {

                    // 记录最后一个排期任务的结束时间
                    $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['end_time'] = $isAssigneeTaskItem['end_time'];

                    // 记录最后一个排期任务的开始时间
                    $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['last_start_time'] = $isAssigneeTaskItem['start_time'];

                    // 记录最后一个排期任务的开始时间 + 当前任务内部最大工序需要消耗工时
                    $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['next_residue_man_hour'] = $nextResidueManHour;

                    // 最后一个任务的最大工时
                    $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['last_man_hour'] = $isAssigneeTaskItem['max_hour'];
                }

                if ($isAssigneeTaskItem['level'] > $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['level']) {
                    $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['level'] = $isAssigneeTaskItem['level'];
                }

                $exitCapacity[$isAssigneeTaskItem['capacity_mark']]['duration'] += $isAssigneeTaskItem['man_hour'];
            } else {

                if ($isAssigneeTaskItem['start_time'] > $this->timeRangeStartTime) {
                    // 不足一个小时舍弃
                    $preResidueManHour = floor(($isAssigneeTaskItem['start_time'] - $this->timeRangeStartTime) / 3600);
                }

                $exitCapacity[$isAssigneeTaskItem['capacity_mark']] = [
                    'start_time' => $isAssigneeTaskItem['start_time'],
                    'pre_residue_man_hour' => $preResidueManHour,
                    'next_residue_man_hour' => $nextResidueManHour,
                    'end_time' => $isAssigneeTaskItem['end_time'],
                    'duration' => $isAssigneeTaskItem['man_hour'],
                    'level' => $isAssigneeTaskItem['level'],
                    'last_start_time' => $isAssigneeTaskItem['start_time'],
                    'first_man_hour' => $isAssigneeTaskItem['max_hour'],
                    'last_man_hour' => $isAssigneeTaskItem['max_hour']
                ];
            }
        }
    }

    /**
     * 执行任务向前自动排期
     * @param $updateSchedulingData
     * @param $canBeScheduledTaskData
     * @param $preResidueManHour
     * @param $param
     * @return array
     */
    public function dealForwardAutoSchedulingTask(&$updateSchedulingData, &$canBeScheduledTaskData, $preResidueManHour, $param)
    {
        $hasMatch = false;
        $preCurrentResidueManHour = 0;
        foreach ($canBeScheduledTaskData[$param['level']] as $canBeScheduledTaskItem) {
            if (!$hasMatch && $canBeScheduledTaskItem['man_hour'] > 0 && $canBeScheduledTaskItem['max_hour'] <= $preResidueManHour) {
                $hasMatch = true;
                $taskStartTime = $param['pre_task_start_time'] + ($canBeScheduledTaskItem['max_hour'] * 3600);
                $taskEndTime = $taskStartTime + ($canBeScheduledTaskItem['man_hour'] * 3600);
                $updateSchedulingData[] = [
                    'id' => $canBeScheduledTaskItem['id'],
                    'plan_start_time' => $taskStartTime,
                    'plan_end_time' => $taskEndTime,
                    'capacity_mark' => $param['capacity_mark']
                ];

                // 修正剩余产能参数
                $param['pre_task_start_time'] = $taskStartTime;
                $param['max_man_hour'] = $canBeScheduledTaskItem['max_hour'];

                $preCurrentResidueManHour = $preResidueManHour - $canBeScheduledTaskItem['max_hour'];
            } else {
                $this->canBeScheduledTaskData[] = $canBeScheduledTaskItem;
            }
        }

        // 当前级别没有剩余数据结束排期
        if (empty($this->canBeScheduledTaskData)) {
            return $updateSchedulingData;
        }

        // 重置未分配任务
        $canBeScheduledTaskData[$param['level']] = $this->canBeScheduledTaskData;
        $this->canBeScheduledTaskData = [];

        if ($hasMatch && $preCurrentResidueManHour > 0) {
            return $this->dealForwardAutoSchedulingTask($updateSchedulingData, $canBeScheduledTaskData, $preCurrentResidueManHour, $param);
        }

        return $updateSchedulingData;
    }

    /**
     * 执行任务向后自动排期
     * @param $updateSchedulingData
     * @param $canBeScheduledTaskData
     * @param $nextResidueManHour
     * @param $param
     * @param bool $autoWrap
     * @return array
     * @throws \Exception
     */
    public function dealBackwardAutoSchedulingTask(&$updateSchedulingData, &$canBeScheduledTaskData, $nextResidueManHour, $param, $autoWrap = false)
    {
        $hasMatch = false;
        $nextCurrentResidueManHour = 0;
        foreach ($canBeScheduledTaskData[$param['level']] as $canBeScheduledTaskItem) {

            if (!$hasMatch && ($param['max_man_hour'] === 0 || ($canBeScheduledTaskItem['man_hour'] < $nextResidueManHour))) {
                $hasMatch = true;
                list($days, $hours) = TaskService::separateManHour($canBeScheduledTaskItem['man_hour'], $this->dayHourLength);
                $taskEndTime = $param['scheduling_start_time'] + ($days * 24 + $hours) * 3600;
                $updateSchedulingData[] = [
                    'id' => $canBeScheduledTaskItem['id'],
                    'plan_start_time' => $param['scheduling_start_time'],
                    'plan_end_time' => $taskEndTime,
                    'capacity_mark' => $param['capacity_mark']
                ];
                list($days, $hours) = TaskService::separateManHour($canBeScheduledTaskItem['max_hour'], $this->dayHourLength);

                // 修正剩余产能参数
                $param['scheduling_start_time'] = $param['scheduling_start_time'] + ($days * 24 + $hours) * 3600;
                $param['max_man_hour'] = $canBeScheduledTaskItem['max_hour'];

                $nextCurrentResidueManHour = $nextResidueManHour - $canBeScheduledTaskItem['max_hour'];

            } else {
                $this->canBeScheduledTaskData[] = $canBeScheduledTaskItem;
            }
        }

        // 当前级别没有剩余数据结束排期
        if (empty($this->canBeScheduledTaskData)) {
            return $updateSchedulingData;
        }

        // 重置未分配任务
        $canBeScheduledTaskData[$param['level']] = $this->canBeScheduledTaskData;
        $this->canBeScheduledTaskData = [];

        if ($hasMatch && $nextCurrentResidueManHour > 0) {
            return $this->dealBackwardAutoSchedulingTask($updateSchedulingData, $canBeScheduledTaskData, $nextCurrentResidueManHour, $param, $autoWrap);
        }

        if ($autoWrap) {
            // 没有剩余时间充值参数继续换行排期
            $param['capacity_mark'] = create_uuid();
            $param['max_man_hour'] = 0;
            $param['scheduling_start_time'] = $this->timeRangeStartTime;

            return $this->dealBackwardAutoSchedulingTask($updateSchedulingData, $canBeScheduledTaskData, $this->timeRangeLength, $param, $autoWrap);
        }

        return $updateSchedulingData;
    }

    /**
     * 自动排期
     * @param $projectId
     * @param $stepCategoryId
     * @return array
     * @throws \Exception
     */
    public function autoScheduling($projectId, $stepCategoryId)
    {

        // 获取当前分类的排期时间范围
        $timeRangeLength = $this->getStepCategoryTimeRange($projectId, $stepCategoryId);
        APP_DEBUG && trace("timerangelength {$timeRangeLength}", "DEBUG");
        $entityModel = model(EntityModel::class);
//        $episode = $entityModel->find($projectId);

        // 获取当前分类所属实体参数
        $belongEntityModuleParam = $this->getStepCategoryBindEntityParam($stepCategoryId);

        // 获取所有需要排期实体
        $entityQueryFields = [
            'id',
            'is_assignee',
            'start_time',
            'end_time',
            'cost_hours as man_hour',
            'is_assignee',
            'is_manual_adjustment'
        ];

        $entityData = $entityModel->field(implode(',', $entityQueryFields))
            ->where(
                [
//                    'initial_episode_code' => $episode['code'],
                    'module_id' => $belongEntityModuleParam['id'],
                    'project_id' => $projectId,
                    'is_generate' => 'yes'
                ])
            ->select();

        if (empty($entityData)) {
            throw_strack_exception("There is no entity data.", ErrorCode::NO_ENTITY_DATA);
        }

        $entityDataDict = array_column($entityData, null, 'id');
        $entityIds = array_column($entityData, 'id');
        $entityStepCategoryTaskData = $this->getStepCategoryTaskMaxManHour($entityIds, $entityDataDict, $belongEntityModuleParam['id']);


        if (empty($entityStepCategoryTaskData)) {
            // 没有任务需要排期
            return [];
        }

        // 已经分配不能自动排期的任务
        $isAssigneeTask = [];

        // 手动调整过不能自动排期的任务
        $isManualAdjustmentTask = [];

        // 工时长度超出当前时间范围的，默认排到最后面
        $overLengthTask = [];

        // 获取需要自动排期的任务
        $needAutoSchedulingTask = [
            "s" => [],
            "a" => [],
            "b" => [],
            "c" => [],
        ];

        foreach ($entityStepCategoryTaskData as $entityStepCategoryTaskItem) {
            if ($entityStepCategoryTaskItem['is_assignee'] === 'yes') {
                $isAssigneeTask[] = $entityStepCategoryTaskItem;
                continue;
            }

            if ($entityStepCategoryTaskItem['is_manual_adjustment'] === 'yes') {
                $isManualAdjustmentTask[] = $entityStepCategoryTaskItem;
                continue;
            }

            if ($entityStepCategoryTaskItem['man_hour'] > $timeRangeLength) {
                $overLengthTask[] = $entityStepCategoryTaskItem;
                continue;
            }

            $needAutoSchedulingTask[$entityStepCategoryTaskItem['level']][] = $entityStepCategoryTaskItem;
        }


        if (empty($needAutoSchedulingTask)) {
            // 没有需要自动排期任务
            return [];
        } else {
            // 需要排序任务按工时长度排序
            foreach (['s', 'a', 'b', 'c'] as $levelCode) {
                if (!empty($needAutoSchedulingTask[$levelCode])) {
                    $needAutoSchedulingTask[$levelCode] = array_sort_by($needAutoSchedulingTask[$levelCode], 'man_hour', 'desc', false);
                }
            }
        }

        /**
         * 按规则排期
         * 1、没有手动调整影响
         * 2、有手动调整和已经分配任务影响
         *
         * 1、按照镜头顺序排产
         * 2、按照难度级别排产，资产：等级决定，镜头：表演种族决定
         * 3、任务等级匹配最优产能排期
         * 4、任务要做同一产能标识，还有产能等级标识（怎么评定组长任务的级别，它是由多个小任务组成的，消耗的是一个或者多个产能）
         * 5、产能标识 s-15, a-12, b-8, c-2 , 字母等级 s > a > b > c，数字越大等级越高
         * 6、决定同一个产能接收下一个任务的开始时间取决于上一个任务内部最大任务时长（这样风险最低）
         */

        /**
         * 先检测不能自动排期产能的等级空余时间
         */
        $exitCapacityData = [];
        $this->checkExitCapacity($exitCapacityData, $isAssigneeTask);
        $this->checkExitCapacity($exitCapacityData, $isManualAdjustmentTask);


        // 更新调整的排期时间数据
        $updateSchedulingData = [];

        /**
         * 优先填充当前不能自动排期的剩余产能
         *
         * start_time : 开始时间
         * end_time ：结束时间
         * pre_residue_man_hour : 前面剩余可排期工时
         * next_residue_man_hour : 后面剩余可排期工时
         * duration ： 时长
         * level : 产能最大级别
         * last_start_time ：最后一个任务的开始时间
         * first_man_hour ：第一个排期内部最大工序耗时
         * last_man_hour：最后一个排期内部最大工序耗时
         */
        if (!empty($exitCapacityData)) {
            foreach ($exitCapacityData as $exitCapacity => $exitCapacityItem) {
                if ($exitCapacityItem['duration'] >= $timeRangeLength) {
                    // 当前产能已经饱和不能增加排期
                    continue;
                } else {
                    // 判断有没有合适任务能插入到当前产能前面
                    if ($exitCapacityItem['pre_residue_man_hour'] > 0) {
                        $this->dealForwardAutoSchedulingTask($updateSchedulingData, $needAutoSchedulingTask, $exitCapacityItem['pre_residue_man_hour'], [
                            'pre_task_start_time' => $exitCapacityItem['start_time'],
                            'capacity_mark' => $exitCapacity,
                            'max_man_hour' => $exitCapacityItem['first_man_hour'],
                            'level' => $exitCapacityItem['level']
                        ]);
                    }

                    // 判断有没有合适任务能插入到当前产能后面
                    if ($exitCapacityItem['next_residue_man_hour'] > 0) {
                        $this->dealBackwardAutoSchedulingTask($updateSchedulingData, $needAutoSchedulingTask, $exitCapacityItem['next_residue_man_hour'], [
                            'scheduling_start_time' => $exitCapacityItem['last_start_time'] + ($exitCapacityItem['last_man_hour'] * 3600),
                            'capacity_mark' => $exitCapacity,
                            'max_man_hour' => $exitCapacityItem['last_man_hour'],
                            'level' => $exitCapacityItem['level']
                        ]);
                    }
                }
            }
        }

        /**
         * 自动排期，目前自动排期，同一标的产能只匹配相同级别任务。
         */
        foreach (['s', 'a', 'b', 'c'] as $levelCode) {
            $currentCapacityMark = create_uuid();
            $this->dealBackwardAutoSchedulingTask($updateSchedulingData, $needAutoSchedulingTask, $timeRangeLength, [
                'scheduling_start_time' => $this->timeRangeStartTime,
                'capacity_mark' => $currentCapacityMark,
                'max_man_hour' => 0,
                'level' => $levelCode
            ], true);

        }


        return [
            'update_data' => $updateSchedulingData, // 返回设置好参数的排期任务数据
            'over_length_data' => $overLengthTask // 超过当前可排期最大长度的任务数据
        ];
    }

    /**
     * @return int
     */
    public function getDayHourLength(): int
    {
        return $this->dayHourLength;
    }

    /**
     * 操作用户的排班数据
     * @param array $task
     * @param int $userUnionId
     * @param string $operation
     * @return array|mixed|\Yurun\Util\YurunHttp\Http\Response
     */
    public function changeUserScheduleCostHour(array $task, int $userUnionId, string $operation)
    {
        if ($task['plan_start_time'] > 0 && $task['plan_end_time'] > 0) {
            $startTime = $task['plan_start_time'];
            $endTime = $task['plan_end_time'];
        } else {
            $startTime = time();
            list($days, $hours) = TaskService::separateManHour($task['man_hour'], $this->getDayHourLength());
            $endTime = $startTime + ($days * 24 + $hours) * 3600;

        }

        $start = date("Y-m-d", $startTime);
        $end = date("Y-m-d", $endTime);

        if ($operation === '+') {
            $route = 'ding_talk/increase_cost_work_hour';
        } else if ($operation === '-') {
            $route = 'ding_talk/decrease_cost_work_hour';
        } else {
            throw_strack_exception('unsupported action', ErrorCode::UNSUPPORTED_OPERATION);
        }
        $requestData = [
            'param' => [
                'filter' => [
                    'user_id' => $userUnionId,
                    'work_date_from' => $start,
                    'work_date_to' => $end,
                    'work_hour' => $task['man_hour'],
                ]
            ]
        ];

        $res = teamones_request('teamones-oauth', $route, $requestData);
        APP_DEBUG && trace(var_export($res, true), 'DEBUG');
        return $res;
    }

    /**
     * 移除集分类排期lock
     * @param $episodeId
     * @param $stepCategoryId
     */
    private function releaseSchedulingLock($episodeId, $stepCategoryId)
    {
        $key = "SchedulingLock-{$episodeId}-{$stepCategoryId}";
        Redis::del($key);
    }

    /**
     * 排期
     * @param int $projectId
     * @param $schedulingStepCategoryId
     * @return void
     * @throws \Throwable
     */
    public function realScheduling(int $projectId, $schedulingStepCategoryId)
    {
        try {
            $res = $this->autoScheduling($projectId, $schedulingStepCategoryId);

            $updateEntityDateArray = $res['update_data'] ?? [];

            if (!empty($updateEntityDateArray)) {
                $entityModel = model(EntityModel::class);
                $taskModel = model(TaskModel::class);

                // 实体数据
                $entityIdList = array_column($updateEntityDateArray, 'id');
                $entityList = $entityModel->where(['id' => ["IN", implode(',', $entityIdList)]])->select();
                $entityMap = array_column($entityList, null, 'id');

                // 任务数据
                $taskList = $taskModel->where(['entity_id' => ["IN", implode(',', $entityIdList)]])->select();

                // 工序分类任务
                $stepCategoryTaskList = array_filter($taskList, function ($task) {
                    return $task['step_type'] == "step_category";
                });
                $stepCategoryTaskMap = array_group_by($stepCategoryTaskList, "entity_id");
                $stepCategoryTaskMap = array_map(function ($taskList) {
                    return array_column($taskList, null, 'step_category_id');
                }, $stepCategoryTaskMap);
                // 工序任务
                $stepTaskList = array_filter($taskList, function ($task) {
                    return $task['step_type'] == "step";
                });

                // 通过实体分组
                $stepTaskMapGroupByEntityId = array_group_by($stepTaskList, 'entity_id');

                $stepTaskMapGroupByEntityId = array_map(function ($stepTaskList) {

                    $stepTaskList = array_group_by($stepTaskList, 'step_category_id');

                    foreach ($stepTaskList as &$taskListGroupByStepCategory) {
                        // 工序任务内部排序
                        $taskListGroupByStepCategory = array_sort_by($taskListGroupByStepCategory, 'pre_task_id');
                    }

                    return $stepTaskList;
                }, $stepTaskMapGroupByEntityId);


                // 工序流关系查询
                $stepWorkflowService = StepWorkflowService::getInstance();
                $stepWorkflowIdList = array_column($entityList, 'step_workflow_id', 'step_workflow_id');
                $stepWorkFlows = $stepWorkflowService->getStepWorkFlows(["IN", implode(',', $stepWorkflowIdList)], 'id,config');
                $stepCategoryRelationMap = [];
                foreach ($stepWorkFlows as $stepWorkFlow) {
                    $config = json_decode($stepWorkFlow['config'], true);
                    $relations = [];
                    foreach ($config['nodes'] as $nodes) {
                        foreach ($nodes as $node) {
                            $relations[] = $node['step_category_id'];
                        }
                    }
                    $stepCategoryRelationMap[$stepWorkFlow['id']] = array_unique($relations);
                }


                $taskModel->startTrans();
                try {

                    foreach ($updateEntityDateArray as $entityUpdateItem) {
                        $entityModel->save([
                            'id' => $entityUpdateItem['id'],
                            'plan_start_time' => $entityUpdateItem['plan_start_time'],
                            'plan_end_time' => $entityUpdateItem['plan_end_time'],
                        ]);

                        // 分类任务更新排期
                        $entityItem = $entityMap[$entityUpdateItem['id']];
                        $startTime = $entityUpdateItem['plan_start_time'];
                        $stepCategoryRelations = $stepCategoryRelationMap[$entityItem['step_workflow_id']] ?? [];
                        $capacityMark = $entityUpdateItem['capacity_mark'];
                        $currentStepCategoryList = $stepCategoryTaskMap[$entityItem['id']] ?? null;
                        if (empty($currentStepCategoryList)) {
                            continue;
                        }
                        foreach ($stepCategoryRelations as $stepCategoryId) {
                            $stepCategoryTask = $currentStepCategoryList[$stepCategoryId] ?? null;
                            if (empty($stepCategoryTask)) {
                                continue;
                            }
                            $stepCategoryTask['man_hour'] = $stepCategoryTask['man_hour'] * ($entityItem['cost_hours'] / $entityItem['estimate_hours']);

                            list($days, $hours) = TaskService::separateManHour($stepCategoryTask['man_hour'], $this->getDayHourLength());
                            $endTime = $startTime + ($days * 24 + $hours) * 3600;

                            $tmpUpdateData = [
                                'id' => $stepCategoryTask['id'],
                                'plan_start_time' => $startTime,
                                'plan_end_time' => $endTime,
                                'capacity_mark' => $capacityMark
                            ];
                            $res['update_data']['step_category'][] = $tmpUpdateData;
                            $taskModel->save($tmpUpdateData);

                            // 工序任务更新排期
                            $currentStepTasks = $stepTaskMapGroupByEntityId[$entityItem['id']][$stepCategoryId] ?? [];
                            $stepTaskStartTime = $startTime;
                            foreach ($currentStepTasks as $stepTask) {
                                $stepTask['man_hour'] = $stepTask['man_hour'] * ($entityItem['cost_hours'] / $entityItem['estimate_hours']);

                                list($days, $hours) = TaskService::separateManHour($stepTask['man_hour'], $this->getDayHourLength());
                                $stepTaskEndTime = $stepTaskStartTime + ($days * 24 + $hours) * 3600;
                                $tmpUpdateData = [
                                    'id' => $stepTask['id'],
                                    'plan_start_time' => $stepTaskStartTime,
                                    'plan_end_time' => $stepTaskEndTime,
                                    'capacity_mark' => $capacityMark
                                ];
                                $res['update_data']['step'][] = $tmpUpdateData;

                                $taskModel->save($tmpUpdateData);
                                $stepTaskStartTime = $stepTaskEndTime;
                            }
                            $startTime = $endTime;
                        }
                    }
                    $taskModel->commit();

                } catch (\Throwable $e) {
                    $taskModel->rollback();
                    throw $e;
                }
            }
        } finally {
            // 释放当前集 分类得排期锁
            $this->releaseSchedulingLock($projectId, $schedulingStepCategoryId);
        }
    }

    /**
     * 是否锁定
     * @param $episodeId
     * @param $stepCategoryId
     * @return bool
     */
    private function isSchedulingLocked($episodeId, $stepCategoryId)
    {
        $key = "SchedulingLock-{$episodeId}-{$stepCategoryId}";
        $lock = Redis::get($key);
        return !empty($lock);
    }


    /**
     * 获得集分类排期lock
     * @param $episodeId
     * @param $stepCategoryId
     * @return bool
     */
    private function getSchedulingLock($episodeId, $stepCategoryId): bool
    {

        if ($this->isSchedulingLocked($episodeId, $stepCategoryId)) {
            return false;
        }
        $key = "SchedulingLock-{$episodeId}-{$stepCategoryId}";
        Redis::setex($key, 60 * 10, 1);
        return true;
    }

    /**
     * 排期
     * @param $param
     * @return string
     * @throws \Throwable
     */
    public function autoSchedulingAsync($param)
    {
        $projectId = $param['filter']['project_id'];
        $stepCategoryId = $param['filter']['step_category_id'];
        $check = $param['check'] ?? 0;

        // 检查状态
        if ($check > 0) {
            if (!$this->isSchedulingLocked($projectId, $stepCategoryId)) {
                throw new LogicException("can`t get scheduling lock ,please retry it later", ErrorCode::CAN_NOT_GET_SCHEDULING_LOCK);
            }
            return "ok";
        }

        if (!$this->getSchedulingLock($projectId, $stepCategoryId)) {
            throw new LogicException("can`t get scheduling lock ,please retry it later", ErrorCode::CAN_NOT_GET_SCHEDULING_LOCK);
        }

        $jobParam = [
            "project_id" => $projectId,
            'step_category_id' => $stepCategoryId,
        ];

        Client::send('task_scheduling', build_queue_data_with_xu($jobParam));
        return true;
    }

}
